package job

import (
	"errors"
	"fmt"
	"gitee.com/gcom/stockdock/comm"
	"gitee.com/gcom/stockdock/core/job/csrc"
	"gitee.com/gcom/stockdock/core/job/ths"
	"gitee.com/gcom/stockdock/core/job/xueqiu"
	s "gitee.com/gcom/stockdock/core/support"
	"github.com/jinzhu/gorm"
	"github.com/robfig/cron/v3"
	"time"
)

type JobHandler func(ctx s.TracerCtx) error

type jobKey string

const (
	JobTest             jobKey = "test"
	JobStockListSh      jobKey = "cron_stock_list_sh"
	JobStockListSz      jobKey = "cron_stock_list_sz"
	JobThsQuote         jobKey = "cron_quote_ths"
	JobXqQuote          jobKey = "cron_quote_xq"
	JobThsIndustry      jobKey = "cron_industry_ths"
	JobThsIndustryStock jobKey = "cron_industry_stocks_ths"
	JobThsCompany       jobKey = "cron_company_ths"
	JobThsQuoteHist     jobKey = "cron_quote_hist_ths"
	JobXqQuoteHist      jobKey = "cron_quote_hist_xq"
)

var jobMap = map[jobKey]JobHandler{}

func (k jobKey) Run(ctx s.TracerCtx) {
	ctx.Resp.Header().Set("Content-Type", "text/event-stream;charset=UTF-8")
	if job, ok := jobMap[k]; ok {
		tx, rec, err := beginJob(ctx, k, "Http-Api")
		if err != nil {
			s.WriteEvent(ctx, "放弃本次任务")
			return
		}

		err = errors.New("")
		var ret = &err
		defer endJob(ctx, rec, ret, tx)
		*ret = job(ctx)
	} else {
		s.WriteErrEvent(ctx, "任务未找到", errors.New("任务未找到"))
	}
}

func init() {
	c := cron.New()
	c.AddFunc("@every 1h", wrapJob("cron_heartbeat", func(ctx s.TracerCtx) error {
		ctx.Logger.Infof("Cron调度正常")
		return nil
	}))
	c.AddFunc("0 8 * * *", wrapJob(JobStockListSh, csrc.UpdateStockListSh))
	c.AddFunc("20 8 * * *", wrapJob(JobStockListSz, csrc.UpdateStockListSz))
	c.AddFunc("30 17 * * *", wrapJob(JobXqQuote, xueqiu.UpdateStockQuote))
	c.AddFunc("0 19 * * *", wrapJob(JobThsQuote, ths.UpdateStockQuote))
	c.AddFunc("0 12 * * *", wrapJob(JobThsIndustry, ths.UpdateIndustryList))
	c.AddFunc("20 12 * * *", wrapJob(JobThsIndustryStock, ths.UpdateIndustryStocks))
	c.AddFunc("0 7 * * Sun", wrapJob(JobThsCompany, ths.UpdateCompany))
	c.Start()
	comm.Log.Infof("定时任务调度启动成功")

	// register none cron job
	jobMap[JobThsQuoteHist] = ths.UpdateStockQuoteHist
	jobMap[JobXqQuoteHist] = xueqiu.UpdateDailyQuoteHist
	jobMap[JobTest] = func(ctx s.TracerCtx) error {
		duration := 30
		for i := duration; i > 0; i-- {
			s.WriteEvent(ctx, fmt.Sprintf("%d/%d", i, duration))
			time.Sleep(time.Second)
		}
		return nil
	}
}

func wrapJob(name jobKey, job JobHandler) func() {
	jobMap[name] = job
	return func() {
		ctx := s.TracerCtxFrom("Job: " + string(name))
		tx, rec, err := beginJob(ctx, name, "Job-Schedule")
		if err != nil {
			s.WriteEvent(ctx, "放弃本次任务")
			return
		}

		err = errors.New("")
		var ret = &err
		defer endJob(ctx, rec, ret, tx)
		*ret = job(ctx)
	}
}

func beginJob(ctx s.TracerCtx, name jobKey, from string) (*gorm.DB, *JobHist, error) {
	tx := ctx.DB.Begin()
	job, err := GetOrCreateJobInTx(tx, string(name))
	if err != nil {
		s.WriteErrEvent(ctx, "查询Job失败", err)
		tx.Rollback()
		return nil, nil, err
	}

	record := JobHist{
		JobId:      job.Id,
		TraceId:    ctx.TraceId,
		Name:       string(name),
		From:       from,
		Status:     JobRunNone,
		StartedAt:  time.Now(),
		FinishedAt: comm.DefaultDateTime,
	}
	err = AddJobHist(ctx.DB, &record)
	if err != nil {
		s.WriteErrEvent(ctx, "创建任务执行记录失败", err)
		tx.Rollback()
		return nil, nil, err
	}
	s.WriteEvent(ctx, "任务开始")
	return tx, &record, err
}

func endJob(ctx s.TracerCtx, record *JobHist, ret *error, tx *gorm.DB) {
	var rec interface{}
	if rec = recover(); rec != nil {
		if err, ok := rec.(error); ok {
			s.WriteErrEvent(ctx, "执行任务失败", err)
		} else {
			s.WriteEvent(ctx, "执行任务失败")
			ctx.Logger.Errorf("执行任务(%s)失败，%v", record.Name, rec)
		}
	}

	record.UpdatedAt = time.Now()
	record.FinishedAt = record.UpdatedAt
	if rec != nil {
		record.Status = JobRunFail
		record.ErrorMsg = fmt.Sprintf("%v", rec)
	} else if *ret != nil {
		record.Status = JobRunFail
		record.ErrorMsg = fmt.Sprintf("%v", *ret)
	} else {
		record.Status = JobRunOk
	}

	var rolledback = false
	if err := UpdateJobHist(ctx.DB, record); err != nil {
		tx.Rollback()
		rolledback = true
		s.WriteErrEvent(ctx, "更新任务执行记录失败", err)
	} else {
		if _, err = UpdateJob(tx, record.JobId, record.StartedAt, record.Status); err != nil {
			tx.Rollback()
			rolledback = true
			s.WriteErrEvent(ctx, "更新任务最近执行状态失败", err)
		}
	}
	s.WriteEvent(ctx, "任务结束")
	if !rolledback {
		tx.Commit()
	}
}
